package com.family.gmall.product.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.family.gmall.common.cache.annotation.GmallCache;
import com.family.gmall.common.constant.RedisConst;
import com.family.gmall.model.product.entity.*;
import com.family.gmall.product.mapper.*;
import com.family.gmall.product.service.ManageService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Auther: family
 * @Date: 2023/7/10 09:05
 * @Description:
 */
@Service
public class ManageServiceImpl implements ManageService {

    @Resource
    private BaseCategory1Mapper baseCategory1Mapper;

    @Resource
    private BaseCategory2Mapper baseCategory2Mapper;

    @Resource
    private BaseCategory3Mapper baseCategory3Mapper;

    @Resource
    private BaseAttrInfoMapper baseAttrInfoMapper;

    @Resource
    private BaseAttrValueMapper baseAttrValueMapper;

    @Resource
    private SkuInfoMapper skuInfoMapper;

    @Resource
    private SkuImageMapper skuImageMapper;

    @Resource
    private BaseCategoryViewMapper baseCategoryViewMapper;

    @Resource
    private SpuSaleAttrMapper spuSaleAttrMapper;

    @Resource
    private SkuSaleAttrValueMapper skuSaleAttrValueMapper;

    @Override
    public List<BaseCategory1> getCategory1() {
        return baseCategory1Mapper.selectList(null);
    }

    @Override
    public List<BaseCategory2> getCategory2(Long category1Id) {
        List<BaseCategory2> list = baseCategory2Mapper.selectList(
                new LambdaQueryWrapper<BaseCategory2>().eq(BaseCategory2::getCategory1Id, category1Id));
        return list;
    }

    @Override
    public List<BaseCategory3> getCategory3(Long category2Id) {
        List<BaseCategory3> list = baseCategory3Mapper.selectList(
                new LambdaQueryWrapper<BaseCategory3>().eq(BaseCategory3::getCategory2Id, category2Id)
        );
        return list;
    }

    @Override
    public List<BaseAttrInfo> getAttrInfoList(Long category1Id, Long category2Id, Long category3Id) {
        return baseAttrInfoMapper.selectBaseAttrInfoList(category1Id, category2Id, category3Id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveAttrInfo(BaseAttrInfo baseAttrInfo) {
        if (baseAttrInfo.getId() != null) {
            baseAttrInfoMapper.updateById(baseAttrInfo);
        } else {
            baseAttrInfoMapper.insert(baseAttrInfo);
        }

        baseAttrValueMapper.delete(
                new LambdaQueryWrapper<BaseAttrValue>().eq(BaseAttrValue::getAttrId, baseAttrInfo.getId()));

        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
        if (attrValueList != null && attrValueList.size() > 0) {
            attrValueList.forEach(item -> {
                item.setAttrId(baseAttrInfo.getId());
                baseAttrValueMapper.insert(item);
            });
        }
    }

    @Override
    public BaseAttrInfo getAttrInfo(Long attrId) {
        BaseAttrInfo baseAttrInfo = baseAttrInfoMapper.selectById(attrId);
        baseAttrInfo.setAttrValueList(getAttrValueList(attrId));
        return baseAttrInfo;
    }

    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SkuImage::getSkuId, skuId);
        List<SkuImage> skuImageList = skuImageMapper.selectList(queryWrapper);
        skuInfo.setSkuImageList(skuImageList);
        return skuInfo;
    }

    @Override
    public SkuInfo getSkuInfoDB(Long skuId) {
        // SkuInfo skuInfo = getSkuInfoRedis(skuId);
        SkuInfo skuInfo = getSkuInfoRedisson(skuId);
        return skuInfo;
    }

    @Override
    public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id) {
        return baseCategoryViewMapper.selectById(category3Id);
    }

    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        if (null != skuInfo) {
            return skuInfo.getPrice();
        }
        return new BigDecimal(0);
    }

    @Override
    public List<SpuSaleAttr> getSpuSaleAttrListCheckBySku(Long skuId, Long spuId) {
        return spuSaleAttrMapper.selectSpuSaleAttrListCheckBySku(skuId, spuId);
    }

    @Override
    public Map getSkuValueIdsMap(Long spuId) {
        Map<Object, Object> map = new HashMap<>();
        List<Map> mapList = skuSaleAttrValueMapper.selectSaleAttrValuesBySpu(spuId);
        if (mapList != null && mapList.size() > 0) {
            mapList.forEach(item -> {
                map.put(item.get("value_ids"), item.get("sku_id"));
            });
        }
        return map;
    }

    @GmallCache(
            key = RedisConst.INDEXPAGE_CATEGORY_TREE_KEY,
            lockKey = RedisConst.INDEXPAGE_CATEGORY_TREE_KEY + "lock",
            ttl = 180
    )
    @Override
    public List<JSONObject> getBaseCategoryList() {
        ArrayList<JSONObject> list = new ArrayList<>();
        List<BaseCategoryView> baseCategoryViewList = baseCategoryViewMapper.selectList(null);

        Map<Long, List<BaseCategoryView>> category1Map = baseCategoryViewList.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id));
        int index = 1;
        for (Map.Entry<Long, List<BaseCategoryView>> entry1 : category1Map.entrySet()) {
            JSONObject category1 = new JSONObject();

            Long category1Id = entry1.getKey();
            List<BaseCategoryView> category2List1 = entry1.getValue();
            category1.put("index", index);
            category1.put("categoryId", category1Id);
            category1.put("categoryName", category2List1.get(0).getCategory1Name());
            index++;

            Map<Long, List<BaseCategoryView>> category2Map = category2List1.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory2Id));
            ArrayList<JSONObject> category2Child = new ArrayList<>();
            category2Map.forEach((key2, value2) -> {
                JSONObject category2 = new JSONObject();
                category2.put("categoryId", key2);
                category2.put("categoryName", value2.get(0).getCategory2Name());
                category2Child.add(category2);

                List<JSONObject> category3Child = new ArrayList<>();

                value2.stream().forEach(categoryView -> {
                    JSONObject category3 = new JSONObject();
                    category3.put("categoryId", categoryView.getCategory3Id());
                    category3.put("categoryName", categoryView.getCategory3Name());
                    category3Child.add(category3);
                });
                category2.put("categoryChild", category3Child);
            });
            category1.put("categoryChild", category2Child);
            list.add(category1);
        }
        return list;
    }

    @Resource
    private BaseTrademarkMapper baseTrademarkMapper;

    @Override
    public BaseTrademark getTrademarkByTmId(Long tmId) {
        return baseTrademarkMapper.selectById(tmId);
    }

    @Override
    public List<BaseAttrInfo> getAttrList(Long skuId) {
        return baseAttrInfoMapper.selectBaseAttrInfoListBySkuId(skuId);
    }

    private List<BaseAttrValue> getAttrValueList(Long attrId) {
        List<BaseAttrValue> baseAttrValues = baseAttrValueMapper.selectList(
                new LambdaQueryWrapper<BaseAttrValue>().eq(BaseAttrValue::getAttrId, attrId));
        return baseAttrValues;
    }

    @Resource
    private RedisTemplate redisTemplate;

    private SkuInfo getSkuInfoRedis(Long skuId) {
        SkuInfo skuInfo = null;
        String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;

        skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);

        try {
            if (skuInfo == null) {
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_PREFIX;
                String uuid = UUID.randomUUID().toString().replace("-", "");
                Boolean isExist = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                if (isExist) {
                    System.out.println("获取到分布式锁！");
                    skuInfo = getSkuInfo(skuId);

                    if (skuInfo == null) {
                        skuInfo = new SkuInfo();
                        redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    }
                    redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    String script = "if redis.call('get', KEYS[1]) ==ARGV[1] then return redis.call('del',KEYS[1]1) else return 0 end";
                    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                    redisScript.setResultType(Long.class);
                    redisScript.setScriptText(script);
                    redisTemplate.execute(redisScript, Arrays.asList(lockKey), uuid);
                    return skuInfo;
                } else {
                    Thread.sleep(1000);
                    return getSkuInfo(skuId);
                }
            } else {
                return skuInfo;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getSkuInfo(skuId);
    }


    @Resource
    private RedissonClient redissonClient;

    private SkuInfo getSkuInfoRedisson(Long skuId) {
        SkuInfo skuInfo = null;
        try {
            String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            skuInfo = (SkuInfo) redisTemplate.opsForValue().get(skuKey);

            if (skuId == null) {
                String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
                RLock lock = redissonClient.getLock(lockKey);
                /*
                第一种： lock.lock();
                第二种:  lock.lock(10,TimeUnit.SECONDS);
                第三种： lock.tryLock(100,10,TimeUnit.SECONDS);
                */
                // 尝试加锁
                boolean res = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX1, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
                if (res) {

                    try {
                        skuInfo = getSkuInfo(skuId);
                        if (skuInfo == null) {
                            skuInfo = new SkuInfo();
                            redisTemplate.opsForValue().set(skuKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                            return skuInfo;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                } else {
                    // 其他线程等待
                    Thread.sleep(1000);
                    return getSkuInfo(skuId);
                }
            } else {
                return skuInfo;
            }


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 为了防止缓存宕机：从数据库中获取数据
        return getSkuInfo(skuId);
    }
}
